#include "treecombobox.h"

#include <QVBoxLayout>
#include <QTreeView>
#include <QAbstractItemModel>
#include <QComboBox>
#include <QHeaderView>
#include <QStyleOptionComboBox>
#include <QStylePainter>
#include <QMouseEvent>
#include <QApplication>
#include <QDesktopWidget>
#include <QStatusBar>

TreeComboBox::TreeComboBox( QWidget* w )
    : QWidget( w ), m_frame( new QFrame( this ) ), m_view( 0 ), m_model( 0 ), m_force( false )
{
    m_frame->setWindowFlags( Qt::Dialog | Qt::FramelessWindowHint );
    QVBoxLayout* vl = new QVBoxLayout( m_frame );
    vl->setSpacing( 0 );
    vl->setMargin( 3 );

    m_iconSize = QSize( 16, 16 );
    m_sizeHint = QComboBox().sizeHint();
    setSizePolicy( QSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed ) );
    setView( new QTreeView );
    m_view->header()->hide();
    m_view->resize( m_view->viewport()->size() );
    m_frame->installEventFilter( this );
}

TreeComboBox::~TreeComboBox()
{ delete m_view; }

bool TreeComboBox::eventFilter( QObject* o, QEvent* e )
{
    if ( o == m_frame ) {
        if ( e->type() == QEvent::WindowDeactivate )
            if ( !rect().contains( mapFromGlobal( QCursor::pos() ) ) )
                hidePopup();
        return QWidget::eventFilter( o, e );
    }

    QEvent::Type t = e->type();
    if ( t == QEvent::Hide ) {
        if ( currentIndex() != m_index ) {
            m_force = false;
            m_view->clearSelection();
            m_view->setCurrentIndex( m_index );
            m_force = true;
        }
        update();
    }
    else if ( t == QEvent::MouseMove ) {
        if ( QMouseEvent* me = dynamic_cast<QMouseEvent*>( e ) ) {
            QModelIndex i = m_view->indexAt( m_view->mapFromGlobal( me->globalPos() ) );
            if ( m_view->currentIndex() != i ) {
                emit highlighted( i );
                m_force = false;
                m_view->clearSelection();
                m_view->setCurrentIndex( i );
                m_force = true;
            }
        }
    }
    return QWidget::eventFilter( o, e );
}

QSize TreeComboBox::sizeHint() const
{ return m_sizeHint; }

int recursiveCount( const QModelIndex& it )
{
    int j = 0;
    if ( !it.parent().isValid() )
        j++;
    for ( int i = 0; i < it.model()->rowCount( it ); i++ ) {
        j++;
        if ( it.model()->rowCount( it.child( i, 0 ) ) )
            j += recursiveCount( it.child( i, 0 ) );
    }
    return j;
}

int TreeComboBox::count() const
{ return recursiveCount( m_model->index( 0, 0 ) ); }

QSize TreeComboBox::iconSize() const
{ return m_iconSize; }

void TreeComboBox::setIconSize( const QSize& s )
{
    if ( m_iconSize != s )
    {
        m_iconSize = s;
        update();
    }
}

void TreeComboBox::paintEvent( QPaintEvent* )
{
    QStyleOptionComboBox o;

    // QStyleOption
    o.initFrom( this );

    // QStyleOptionComplex
    o.activeSubControls = 0;
    o.subControls = QStyle::SC_ComboBoxEditField | QStyle::SC_ComboBoxArrow | QStyle::SC_ComboBoxFrame;

    // QStyleOptionComboBox
    QModelIndex i = currentIndex();
    o.currentIcon = i.data( Qt::DecorationRole ).value<QIcon>();
    o.iconSize = m_view && m_view->iconSize() != QSize( -1, -1 ) ? m_view->iconSize() : iconSize();
    o.currentText = i.data( Qt::DisplayRole ).toString();
    o.editable = false;
    o.frame = true;
    o.popupRect = QRect();

    if ( !m_frame->isVisible() && rect().contains( mapFromGlobal( QCursor::pos() ) ) )
        o.state |= QStyle::State_MouseOver;

    if ( m_frame->isVisible() )
        o.state |= QStyle::State_On;

    QStylePainter p( this );
    p.drawComplexControl( QStyle::CC_ComboBox, o );
    p.drawControl( QStyle::CE_ComboBoxLabel, o );
}

void TreeComboBox::hideEvent( QHideEvent* )
{ hidePopup(); }

void TreeComboBox::enterEvent( QEvent* )
{ update(); }

void TreeComboBox::leaveEvent( QEvent* )
{ update(); }

void TreeComboBox::mousePressEvent( QMouseEvent* )
{
    if ( !m_view )
        return;
    m_frame->isVisible() ? hidePopup() : showPopup();
}

void TreeComboBox::hidePopup()
{
    if ( m_frame->isVisible() )
        m_frame->hide();
}

void TreeComboBox::calculPopupGeometry() // code copied from QComboBox  original class from Trolltech, arrange to feet my needs
{
    //int itemHeight = mView->sizeHintForIndex( mModel->index( 0, 0 ) ).height();
    QRect listRect( rect() );
    //listRect.setHeight(itemHeight * count() +( 2 *mFrame->layout()->spacing() ) +( 2 *mFrame->frameWidth() ) );
    QRect screen = QApplication::desktop()->screenGeometry(this);
    QPoint below = mapToGlobal(listRect.bottomLeft());
    int belowHeight = screen.bottom() - below.y();
    QPoint above = mapToGlobal(listRect.topLeft());
    int aboveHeight = above.y() - screen.y();

    // make sure the widget fits on screen
        if (listRect.width() > screen.width() )
        listRect.setWidth(screen.width());
    if (mapToGlobal(listRect.bottomRight()).x() > screen.right()) {
        below.setX(screen.x() + screen.width() - listRect.width());
        above.setX(screen.x() + screen.width() - listRect.width());
    }
    if (mapToGlobal(listRect.topLeft()).x() < screen.x() ) {
        below.setX(screen.x());
        above.setX(screen.x());
    }

    if (listRect.height() <= belowHeight) {
        listRect.moveTopLeft(below);
    } else if (listRect.height() <= aboveHeight) {
        listRect.moveBottomLeft(above);
    } else if (belowHeight >= aboveHeight) {
        listRect.setHeight(belowHeight);
        listRect.moveTopLeft(below);
    } else {
        listRect.setHeight(aboveHeight);
        listRect.moveBottomLeft(above);
    }

    m_frame->setGeometry( listRect );
}

void TreeComboBox::showPopup()
{
    if ( !m_frame->isVisible() && m_view ) {
        m_index = currentIndex();
        calculPopupGeometry();
        m_frame->show();
        update();
    }
}

QTreeView* TreeComboBox::view() const
{ return m_view; }

void TreeComboBox::setView( QTreeView* t )
{
    if ( m_view == t )
        return;
    delete m_view;
    m_view = t;
    if ( m_view ) {
        if ( m_frame->layout()->count() )
            qobject_cast<QVBoxLayout*>( m_frame->layout() )->insertWidget( 0, m_view );
        else {
            m_frame->layout()->addWidget( m_view );
            QStatusBar* sb = new QStatusBar( m_frame );
            sb->setFixedHeight( 16 );
            m_frame->layout()->addWidget( sb );
        }
        m_view->setEditTriggers( QAbstractItemView::NoEditTriggers );
        m_view->setMouseTracking( true );
        m_view->viewport()->installEventFilter( this );
        setModel( m_model );
        connect( m_view, SIGNAL( activated( const QModelIndex& ) ), this, SLOT( internal_activated( const QModelIndex& ) ) );
        connect( m_view, SIGNAL( clicked( const QModelIndex& ) ), this, SLOT( internal_clicked( const QModelIndex& ) ) );
    }
}

QAbstractItemModel* TreeComboBox::model() const
{ return m_model; }

void TreeComboBox::setModel( QAbstractItemModel* m )
{
    if ( m_model != m )
        m_model = m;
    if ( m_view && m_view->model() != m_model ) {
        m_view->setModel( m_model );
        connect( m_view->selectionModel(), SIGNAL( currentChanged( const QModelIndex&, const QModelIndex& ) ), this, SLOT( internal_currentChanged( const QModelIndex&, const QModelIndex& ) ) );
    }
}

QModelIndex TreeComboBox::rootIndex() const
{ return m_view ? m_view->rootIndex() : QModelIndex(); }

void TreeComboBox::setRootIndex( const QModelIndex& i )
{ if ( m_view ) m_view->setRootIndex( i ); }

QModelIndex TreeComboBox::currentIndex() const
{
    if ( m_view )
        return m_frame->isVisible() ? m_index : m_view->currentIndex();
    return QModelIndex();
}

void TreeComboBox::setCurrentIndex( const QModelIndex& i )
{
    if ( m_view && ( currentIndex() != i || !i.isValid() ) ) {
        m_index = i;
        m_force = true;
        m_view->clearSelection();
        m_view->setCurrentIndex( i );
        m_force = false;
        update();
    }
}

void TreeComboBox::expandAll()
{ if ( m_view ) m_view->expandAll(); }

void TreeComboBox::internal_activated( const QModelIndex& i )
{
    if ( m_index != i ) {
        m_index = i;
        emit currentChanged( i );
    }
    emit activated( i );
    hidePopup();
}

void TreeComboBox::internal_clicked( const QModelIndex& i )
{
    if ( m_index != i ) {
        m_index = i;
        emit currentChanged( i );
    }
    emit clicked( i );
    hidePopup();
}

void TreeComboBox::internal_currentChanged( const QModelIndex& c, const QModelIndex& )
{
    if ( m_force )
        emit currentChanged( c );
}
